package gogen

import (
	_ "embed"
	"fmt"
	"io"
	"os"
	"path"
	"strings"

	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
	apiutil "github.com/zeromicro/go-zero/tools/goctl/api/util"
	"github.com/zeromicro/go-zero/tools/goctl/config"
	"github.com/zeromicro/go-zero/tools/goctl/util"
	"github.com/zeromicro/go-zero/tools/goctl/util/format"
)

//go:embed types.tpl
var typesTemplate string

// BuildTypes gen types to string
func BuildTypes(types []spec.Type) (string, map[string]struct{}, error) {
	var builder strings.Builder
	res := map[string]struct{}{}
	first := true
	for _, tp := range types {
		if first {
			first = false
		} else {
			builder.WriteString("\n\n")
		}
		pkgs, err := writeType(&builder, tp)
		if err != nil {
			return "", nil, apiutil.WrapErr(err, "Type "+tp.Name()+" generate error")
		}

		if pkgs == nil || len(pkgs) == 0 {
			continue
		}

		for s, _ := range pkgs {
			if len(s) == 0 {
				continue
			}
			res[s] = struct{}{}
		}
	}

	return builder.String(), res, nil
}

func genDomain(dir string, cfg *config.Config, api *spec.ApiSpec) error {
	val, pkgs, err := BuildTypes(api.Types)
	if err != nil {
		return err
	}

	typeFilename, err := format.FileNamingFormat(cfg.NamingFormat, typesPacket)
	if err != nil {
		return err
	}

	typeFilename = typeFilename + ".go"
	filename := path.Join(dir, typesDir, typeFilename)
	os.Remove(filename)

	if pkgs != nil && len(pkgs) > 0 {
		pkgStr := "\n\nimport (\n"
		for s, _ := range pkgs {
			if len(s) == 0 {
				continue
			}
			pkgStr += fmt.Sprintf("\"%s\"\n", s)
		}
		pkgStr += ")\n\n"
		val = pkgStr + val
	}

	return genFile(fileGenConfig{
		dir:             dir,
		subdir:          typesDir,
		filename:        typeFilename,
		templateName:    "typesTemplate",
		category:        category,
		templateFile:    typesTemplateFile,
		builtinTemplate: typesTemplate,
		data: map[string]any{
			"types": val,
			//"containsTime": false,
		},
	})
}

func writeType(writer io.Writer, tp spec.Type) (map[string]struct{}, error) {
	structType, ok := tp.(spec.DefineStruct)
	if !ok {
		return nil, fmt.Errorf("unspport struct type: %s", tp.Name())
	}

	res := map[string]struct{}{}

	fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name()))
	for _, member := range structType.Members {
		if member.IsInline {
			if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type.Name())); err != nil {
				return nil, err
			}

			continue
		}

		//fmt.Println("domain write type ", member.Type, member.Name, member.Tag, member.GetComment(), member.Type)
		pkg, err := writeProperty(writer, member.Name, member.Tag, member.GetComment(), member.Type, 1)
		if err != nil {
			return nil, err
		}
		res[pkg] = struct{}{}
	}
	fmt.Fprintf(writer, "}")
	return res, nil
}
